#include <os/debug.h>
#include <sys/ioctl.h>
#include <os/driver.h>
#include<lib/assert.h>
#include <lib/stdio.h>
#include <lib/stdlib.h>
#include <lib/stdint.h>
#include <lib/list.h>
#include <lib/type.h>
#include <sys/ioctl.h>
#include <sys/res.h>
#include <driver/view/view.h>
#include <driver/view/env.h>
#include <driver/view/hal.h>
#include <driver/view/refresh.h>
#include <driver/view/mouse.h>
#include <driver/view/keyboard.h>
#include <driver/view/color.h>
#include <driver/view/screen.h>
#include <driver/view/render.h>
#include <driver/view/msg.h>
#include <driver/view/core.h>
#include <driver/view/debug.h>
#include <os/spinlock.h>
#include <os/msgpool.h>

uint16_t *view_id_map;
int view_last_x; // last view x pos
int view_last_y; // last view y pos
int view_top_z = -1;
static int view_next_id = 0;

LIST_HEAD(view_global_list_head);
LIST_HEAD(view_show_list_head);

// view global var lock
DEFINE_SPIN_LOCK(view_global_lock);
// view list lock
DEFINE_SPIN_LOCK(view_list_lock);

int ViewInit()
{
    size_t id_map_size = view_screen.width * view_screen.height * sizeof(uint16_t);
    view_id_map = KMemAlloc(id_map_size);
    if (!view_id_map)
        return -1;

#ifdef VIEW_INIT_DEBUG
// KPrint("[view] view map at %x size %dKB\n", view_id_map, id_map_size / 1024);
#endif

    memset(view_id_map, 0, id_map_size);

    view_last_x = view_last_y = 0;
    if (ViewRefreshInit() < 0)
    {
        KMemFree(view_id_map);
        return -1;
    }
#ifdef VIEW_INIT_DEBUG
    KPrint("[view] view init done\n");
#endif
    return 0;
}

void ViewExit()
{
    view_t *view, *next;
    list_traversal_all_owner_to_next_safe(view, next, &view_global_list_head, global_list)
    {
        ViewDestroy(view);
    }
    list_init(&view_show_list_head);
    list_init(&view_global_list_head);
    KMemFree(view_id_map);
    view_id_map = NULL;
    view_top_z = -1;
    view_next_id = 0;
}

size_t ViewGetVramSize(view_t *view)
{
    if (!view)
        return 0;
    if (!view->section)
        return 0;
    return view->section->size;
}

void *ViewGetVramStart(view_t *view)
{
    if (!view)
        return NULL;
    if (!view->section)
        return NULL;
    return view->section->addr;
}

void ViewMaxSizeRepair(int *width, int *height)
{
    if (*width > VIEW_MAX_SIZE_WIDTH)
        *width = VIEW_MAX_SIZE_WIDTH;
    if (*height > VIEW_MAX_SIZE_HEIGHT)
        *height = VIEW_MAX_SIZE_HEIGHT;
}

view_t *ViewCreate(int x, int y, int width, int height, int type)
{
    view_t *view = KMemAlloc(sizeof(view_t));
    if (!view)
        return NULL;

    // alloc section
    view->section = ViewSectionCreate(width, height);
    if (!view->section)
    {
        KPrint(PRINT_ERR "view: new section failed!\n");
        KMemFree(view);
        return NULL;
    }

    // alloc msgpool
    view->msgpool = MsgpoolCreate(sizeof(view_msg_t), VIEW_MSGPOOL_MSG_NUM);
    if (!view->msgpool)
    {
        KPrint("view: create msgpool failed!\n");
        ViewSectionDestroy(view->section);
        KMemFree(view);
        return NULL;
    }
    view->x = x;
    view->y = y;
    view->id = view_next_id++;
    view->z = -1;
    // repair view size if need to fit the defined size
    ViewMaxSizeRepair(&width, &height);
    view->width = width;
    view->height = height;
    view->width_min = VIEW_RESIZE_SIZE_MIN;
    view->height_min = VIEW_RESIZE_SIZE_MIN;
    view->attr = 0;

    ViewSetType(view, type);

    // region init
    int i;
    for (i = 0; i < VIEW_DRAG_REGION_NUM; i++)
    {
        ViewRegionReset(&view->drag_regions[i]);
    }
    ViewRegionInit(&view->drag_regions[0], 0, 0, view->width, view->height);
    ViewRegionInit(&view->resize_regions, VIEW_RESIZE_BORDER_SIZE, VIEW_RESIZE_BORDER_SIZE, view->width - VIEW_RESIZE_BORDER_SIZE, view->height - VIEW_RESIZE_BORDER_SIZE);

    // init list
    list_init(&view->list);
    list_init(&view->global_list);

    // init spinlock
    SpinLockInit(&view->lock);

    SpinLockDisInterrupt(&view_global_lock);
    list_add_tail(&view->global_list, &view_global_list_head);
    SpinUnlockEnInterrupt(&view_global_lock);

    // draw fill
    ViewRenderRectfill(view, 0, 0, view->width, view->height, VIEW_WHITE);
    return view;
}

view_t *ViewFindById(int id)
{
    view_t *view;
    SpinLockDisInterrupt(&view_global_lock);
    list_traversal_all_owner_to_next(view, &view_global_list_head, global_list)
    {
        if (view->id == id)
        {
            SpinUnlockEnInterrupt(&view_global_lock);
            return view;
        }
    }
    SpinUnlockEnInterrupt(&view_global_lock);
    return NULL;
}

view_t *ViewFindByZ(int z)
{
    view_t *view;
    SpinLockDisInterrupt(&view_global_lock);
    list_traversal_all_owner_to_next(view, &view_global_list_head, global_list)
    {
        if (view->z == z)
        {
            SpinUnlockEnInterrupt(&view_global_lock);
            return view;
        }
    }
    SpinUnlockEnInterrupt(&view_global_lock);
    return NULL;
}

list_t *ViewGetShowList()
{
    return &view_show_list_head;
}

int ViewDestroy(view_t *view)
{
    if (!view)
        return -1;

    // record last destroy view
    view_last_x = view->x;
    view_last_y = view->y;

    if (MsgpoolDestroy(view->msgpool) < 0)
        return -1;

    if (ViewSectionDestroy(view->section) < 0)
        return -1;

    // del from global list
    SpinLock(&view_global_lock);
    list_del(&view->global_list);
    SpinUnlock(&view_global_lock);

    KMemFree(view);
    return 0;
}

int ViewGetMsg(view_t *view, void *buff, int flags)
{
    if (!view)
        return -1;
    if (!view->msgpool)
        return -1;
    if (flags & VIEW_MSG_NOWAIT)
    {
        if (MsgpoolTryGet(view->msgpool, buff, NULL) < 0)
            return -1;
    }
    else
    {
        if (MsgpoolGet(view->msgpool, buff, NULL) < 0)
            return -1;
    }
    return 0;
}

int ViewPutMsg(view_t *view, void *buff, int flags)
{
    if (!view)
        return -1;
    if (!view->msgpool)
        return -1;
    if (flags & VIEW_MSG_NOWAIT)
    {
        if (MsgpoolTryPut(view->msgpool, buff, sizeof(view_msg_t)) < 0)
            return -1;
    }
    else
    {
        if (MsgpoolPut(view->msgpool, buff, sizeof(view_msg_t)) < 0)
            return -1;
    }
    return 0;
}

void ViewClear(view_t *view)
{
    if (view && view->section)
        ViewSectionClear(view->section);
}

int ViewSetType(view_t *view, int type)
{
    if (type > VIEW_TYPE_FLOAT)
        return -1;

    view->type = type;
    if (type == VIEW_TYPE_WINDOWS)
    {
        view->attr |= VIEW_ATTR_MOVEABLE | VIEW_ATTR_RESIZEABLE;
    }
    return 0;
}

int ViewGetType(view_t *view)
{
    return view->type;
}

int ViewSetSizeMin(view_t *view, int width, int height)
{
    if (!view)
        return -1;
    // prevent above limit
    ViewMaxSizeRepair(&width, &height);
    if (view->width < VIEW_RESIZE_SIZE_MIN)
        view->width_min = VIEW_RESIZE_SIZE_MIN;
    if (view->height < VIEW_RESIZE_SIZE_MIN)
        view->height_min = VIEW_RESIZE_SIZE_MIN;
    return 0;
}

int ViewSetDragRegion(view_t *view, view_region_t *region)
{
    if (!view)
        return -1;
    ViewRegionCopy(view->drag_regions, region);
}

int ViewShow(view_t *view)
{
    // KPrint("[view] show view %d z=%d\n", view->id, view->z);

    if (!view)
        return -1;
    if (view->z < 0)
        return ViewMoveToTop(view);
    return ViewMoveUpperTop(view);
}

void ViewGlobalDump()
{
    view_t *view = NULL;
    list_traversal_all_owner_to_next(view, &view_global_list_head, global_list)
    {
        KPrint("view id %d z %d x %d y %d type %x\n", view->id, view->z, view->x, view->y, view->type);
    }
}

void ViewShowDump()
{
    view_t *view = NULL;
    list_traversal_all_owner_to_next(view, &view_show_list_head, list)
    {
        KPrint("view id %d z %d x %d y %d type %x\n", view->id, view->z, view->x, view->y, view->type);
    }
}

int ViewSetXY(view_t *view, int x, int y)
{
    if (!view)
        return -1;

    int old_x = view->x;
    int old_y = view->y;

    view->x = x;
    view->y = y;

    SpinLockDisInterrupt(&view->lock);
    if (view->z >= 0)
    {
        // KPrint("[view] view %d setxy z %d\n", view->id, view->z);
        int x0, y0, x1, y1;
        x0 = min(old_x, x);
        y0 = min(old_y, y);
        x1 = max(old_x + view->width, x + view->width);
        y1 = max(old_y + view->height, y + view->height);
        ViewRefreshMap(x0, y0, x1, y1, 0);
        ViewRefreshByZ(x0, y0, x1, y1, 0, view->z);
    }
    // KPrint("[view] set pos to x %d y %d\n", x, y);
    SpinUnlockEnInterrupt(&view->lock);
    return 0;
}

view_t *ViewGetTop()
{
    SpinLock(&view_global_lock);
    view_t *view = list_last_owner(&view_show_list_head, view_t, list);
    SpinUnlock(&view_global_lock);
    return 0;
}

int ViewResize(view_t *view, int x, int y, uint32_t width, uint32_t height)
{
    if (!view || !width || !height)
        return -1;

    if (!view->section)
        return -1;

    ViewMaxSizeRepair(&width, &height);
    view_section_t *new_section = ViewSectionCreate(width, height);
    if (!new_section)
        return -1;

    // clear resource
    ViewSectionClear(view->section);
    // reset view pos
    ViewSetXY(view, x, y);
    // destroy old buffer
    ViewSectionDestroy(view->section);
    // rebind buffer
    view->section = new_section;
    view->height = height;
    view->width = width;
    // reset region size
    ViewRegionInit(&view->resize_regions, VIEW_RESIZE_BORDER_SIZE, VIEW_RESIZE_BORDER_SIZE, view->width - VIEW_RESIZE_BORDER_SIZE, view->width - VIEW_RESIZE_BORDER_SIZE);

    #ifdef VIEW_DEBUG_FUNC
    KPrint("[view] resize view %d ok\n", view->id);
    #endif 
    return 0;
}

static void __ViewAdjustByZ(view_t *view, int z)
{
    view_t *tmp=NULL;
    view_t *old_view=NULL;

    if (z == view->z) // height same,no action
        return;

    if (z > view_top_z) // height above limit
    {
        z = view_top_z;
    }

    SpinLock(&view_list_lock);
    // del list
    list_del_init(&view->list);

    // adjust to top view
    if (z == view_top_z)
    {
        // ohter view under high
        list_traversal_all_owner_to_next(tmp, &view_show_list_head, list)
        {
            if (tmp->z > view->z)
            {
                tmp->z--;
            }
        }
        view->z = z;
        list_add_tail(&view->list, &view_show_list_head);
        SpinUnlock(&view_list_lock);

        // refresh view
        ViewRefreshMap(view->x, view->y, view->x + view->width, view->y + view->height, view->z);
        ViewRefreshByZ(view->x, view->y, view->x + view->width, view->y + view->height, z, z);

    }
    else
    {
        // no hight view
        if (z > view->z)
        {
            // other view height that between new and old under height
            list_traversal_all_owner_to_next(tmp, &view_show_list_head, list)
            {
                if (tmp->z > view->z && tmp->z <= z)
                {
                    if (tmp->z == z)
                    {
                        old_view = tmp;
                    }
                    tmp->z--;
                }
            }
            assert(old_view);
            view->z = z;
            list_add_after(&view->list, &old_view->list);
            SpinUnlock(&view_list_lock);

            // refresh view
            ViewRefreshMap(view->x, view->y, view->x + view->width, view->y + view->height, z);
            ViewRefreshByZ(view->x, view->y, view->x + view->width, view->y + view->height, z, z);
        }
        else
        {
            // new hight is lower than orgin
            if (z < view->z)
            {
                // other view height that between new and old upper height
                list_traversal_all_owner_to_next(tmp, &view_show_list_head, list)
                {
                    if (tmp->z < view->z && tmp->z >= z)
                    {
                        if (tmp->z == z)
                        {
                            old_view = tmp;
                        }
                        tmp->z++;
                    }
                }
                assert(old_view);
                view->z = z;
                list_add_before(&view->list, &old_view->list);
                SpinUnlock(&view_list_lock);

                // refresh view
                ViewRefreshMap(view->x, view->y, view->x + view->width, view->y + view->height, z);
                ViewRefreshByZ(view->x, view->y, view->x + view->width, view->y + view->height, z, view_top_z);
            }
        }
    }

    //ViewShowDump();
}

// hide view by set deepth to down
static void __ViewHiddenByZ(view_t *view, int z)
{
    int old_z = view->z;

    SpinLock(&view_list_lock);
    list_del_init(&view->list);
    // current view no top view,adjust upper view to down one heights.
    if (view_top_z > old_z)
    {
        view_t *tmp;
        list_traversal_all_owner_to_next(tmp, &view_show_list_head, list)
        {
            if (tmp->z > view->z)
            {
                tmp->z--;
            }
        }
    }
    // top view down height
    view_top_z--;
    // set view to hidden
    view->z = -1;
    SpinUnlock(&view_list_lock);
    // refresh view
    ViewRefreshMap(view->x, view->y, view->x + view->width, view->y + view->height, 0);
    ViewRefreshByZ(view->x, view->y, view->x + view->width, view->y + view->height, 0, view_top_z);
}

// show view by set deepth
static void __ViewShowByZ(view_t *view, int z)
{
    view_t *tmp;
    view_t *old_view=NULL;

    // adjust height
    if (z > view_top_z)
    {
        view_top_z++;
        z = view_top_z;
    }
    else
    {
        view_top_z++;
    }

    SpinLock(&view_list_lock);
    // top view
    if (z == view_top_z)
    {
        view->z = z;
        list_add_tail(&view->list, &view_show_list_head);
        SpinUnlock(&view_list_lock);
        // refresh
        ViewRefreshMap(view->x, view->y, view->x + view->width, view->y + view->height, z);
        ViewRefreshByZ(view->x, view->y, view->x + view->width, view->y + view->height, z, z);
    }
    else
    {
        // find same height view
        list_traversal_all_owner_to_next(tmp, &view_show_list_head, list)
        {
            if (tmp->z == z)
            {
                old_view = tmp;
                break;
            }
        }
        assert(old_view);
        tmp = NULL;
        // all view up height after current view height
        list_traversal_all_owner_to_next(tmp, &view_show_list_head, list)
        {
            if (tmp->z >= old_view->z)
            {
                tmp->z++;
            }
        }
        view->z = z;
        // insert view
        list_add_before(&view->list, &old_view->list);
        SpinUnlock(&view_list_lock);
        // refresh
        ViewRefreshMap(view->x, view->y, view->x + view->width, view->y + view->height, z);
        ViewRefreshByZ(view->x, view->y, view->x + view->width, view->y + view->height, z, view_top_z);
    }
}

void ViewSetZ(view_t *view, int z)
{
    if (view->z == z) // had been fixed to targe deepth
        return;

    SpinLockDisInterrupt(&view->lock);
    SpinLock(&view_list_lock);
    if (list_find(&view->list, &view_show_list_head)) // view in show list
    {
        SpinUnlock(&view_list_lock);

        if (z >= 0)
        {
            #ifdef DEBUG_VIEW_FUNC
            KPrint("[view] setz: adjust z %d to %d\n", view->z, z);
            #endif
            __ViewAdjustByZ(view, z);
        }
        else
        {
            #ifdef DEBUG_VIEW_FUNC
            KPrint("[view] setz: hide z %d to %d\n", view->z, z);
            #endif
            __ViewHiddenByZ(view, z);
        }
    }
    else // insert view
    {
        SpinUnlock(&view_list_lock);

        if (z >= 0)
        {
            #ifdef DEBUG_VIEW_FUNC
            KPrint("[view] insert new view to z %d\n", z);
            #endif 
            __ViewShowByZ(view, z);
        }
    }
    SpinUnlockEnInterrupt(&view->lock);
}

int ViewDragRectCheck(view_t *view, int x, int y)
{
    view_region_t *rect;
    int i;
    for (i = 0; i < VIEW_DRAG_REGION_NUM; i++)
    {
        rect = view->drag_regions;

        if (ViewRegionValid(rect))
        {
            if (ViewRegionInRange(rect, x, y))
            {
                return 1;
            }
        }
    }
    return 0;
}
